/*
 * Copyright 2010 dev7.jp
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jp.dev7.android.util;

import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import jp.dev7.android.database.ProtoBufCursor;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.protobuf.Message;
import com.google.protobuf.MessageLite;

public class Dbs {

    public static final String FIELD_NAME = "serialized";

    public static abstract class FuncCur<T> implements Function<Cursor, T> {
        final public T apply(Cursor input) {
            return apply(
                input.getInt(0),
                input.getString(1),
                new Date(input.getLong(2)));
        }

        public abstract T apply(Integer id, String value, Date updateAt);
    }

    private Dbs() {
    }

    public static ProtoBufCursor toCursor(MessageLite message) {
        ProtoBufCursor result = new ProtoBufCursor();
        result.getExtras().putByteArray(Dbs.FIELD_NAME, message.toByteArray());
        return result;
    }

    public static void createTable(SQLiteDatabase db, String... tableNames) {
        for (String tableName : tableNames) {
            db.execSQL(String.format("CREATE TABLE IF NOT EXISTS %s ("
                + "_ID INTEGER PRIMARY KEY,"
                + "VALUE TEXT,"
                + "UPDATED_AT INTEGER)", tableName));
        }
    }

    public static <T> Iterable<T> itr(final Cursor cursor,
            final Function<Cursor, T> f) {
        if (cursor == null) {
            return null;
        }
        cursor.moveToPosition(-1);
        return new Iterable<T>() {
            @Override
            public Iterator<T> iterator() {
                return new Iterator<T>() {
                    @Override
                    public void remove() {
                        throw new IllegalAccessError("no support");
                    }

                    @Override
                    public T next() {
                        cursor.moveToNext();
                        return f.apply(cursor);
                    }

                    @Override
                    public boolean hasNext() {
                        return cursor.getPosition() != cursor.getCount() - 1;
                    }
                };
            }
        };
    }

    public static <T> List<T> loadAsList(SQLiteDatabase db, String tableName,
            FuncCur<T> f) {
        return toList(load(db, tableName), f);
    }

    public static <T> List<T> toList(Cursor cursor, Function<Cursor, T> f) {
        if (cursor == null) {
            return Lists.newArrayList();
        }
        try {
            return Lists.newArrayList(itr(cursor, f));
        } finally {
            cursor.close();
        }
    }

    public static Cursor load(SQLiteDatabase db, String tableName) {
        Cursor cursor =
            db.query(
                tableName,
                new String[] { "_ID", "VALUE", "UPDATED_AT" },
                null,
                null,
                null,
                null,
                "_ID");
        return cursor;
    }

    public static int save(SQLiteDatabase db, String tableName, int id,
            String value) {
        ContentValues contentValue = new ContentValues();
        contentValue.put("_ID", id);
        contentValue.put("VALUE", value);
        return insertOrUpdate(db, tableName, contentValue);
    }

    public static int saveAll(SQLiteDatabase db, String tableName,
            Map<Integer, String> map) {
        int result = 0;
        for (ContentValues value : toContentValues(map)) {
            result += insertOrUpdate(db, tableName, value);
        }
        return result;
    }

    public static List<ContentValues> toContentValues(
            final Map<Integer, String> map) {
        return Lists.transform(
            Lists.newArrayList(map.keySet()),
            new Function<Integer, ContentValues>() {
                @Override
                public ContentValues apply(Integer id) {
                    ContentValues r = new ContentValues();
                    r.put("_ID", id);
                    r.put("VALUE", map.get(id));
                    return r;
                }
            });
    }

    /**
     * なければ登録、あれば更新.
     * 
     * 更新日時を自動セットする.
     * 
     * @param db
     *            -
     * @param tableName
     *            -
     * @param value
     *            -
     * @return 件数
     */
    public static int insertOrUpdate(SQLiteDatabase db, String tableName,
            ContentValues value) {
        value.put("UPDATED_AT", new Date().getTime());
        Cursor qs =
            db.query(
                tableName,
                new String[] { "_ID" },
                "_ID" + "=?",
                new String[] { value.get("_ID").toString() },
                null,
                null,
                null);
        try {
            if (qs.getCount() == 0) {
                Log.i("haven", "insert " + tableName + " " + value);
                db.insert(tableName, null, value);
                return 1;
            } else {
                Log.i("haven", "update " + tableName + " " + value);
                return db.update(
                    tableName,
                    value,
                    "_ID" + "=?",
                    new String[] { value.get("_ID").toString() });
            }
        } finally {
            qs.close();
        }
    }

    public static <T extends Message> Cursor newProtoBufCursor(
            Iterable<T> messages) {
        ProtoBufCursor result = new ProtoBufCursor();
        for (T m : messages) {
            result.addRow(new Object[] { m.toByteArray() });
        }
        return result;
    }

    public static <T> MatrixCursor newMatrixCursor(String[] columnNames,
            Iterable<T> values, Function<T, Object[]> f) {
        MatrixCursor result = new MatrixCursor(columnNames);
        for (T t : values) {
            result.addRow(f.apply(t));
        }
        return result;
    }

}
